<?php
/**
 * Date: 2018/6/6
 * Time: 10:49
 * Author: zeronlee
 */

namespace App\Services\Vuls;

use App\Models\Flaw\Level;
use Carbon\Carbon;
use Illuminate\Support\Facades\DB;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\View;
use App\Services\Email\Email;
use App\Models\Flaw\Task;
use App\Models\Flaw\Vuls;

class SendEmail
{

    /**
     * 邮件类型
     */
    const ASSIGN = 1;//审核
    const SHUTDOWN = 2;//关闭
    const ISTIMEOUT = 3;//即将超时
    const HASTIMEOUT = 4;//已经超时
    const UNCONFIRM = 5;//未修复
    const REJECT = 6;//驳回

    /**
     * 非正文公共信息
     */
    public $publics = [];

    /**
     * 邮件类型
     */
    public $type;

    /**
     * 邮件主题
     */
    public $subject = '';

    /**
     * @var string
     */
    public $userConfig = [];

    public function __construct () {
        //更改读取数据库配置
        $userSettingJson = DB::table('sys_config')->where(['module'=>'email','disabled'=>0])->value('setting');
        $this->userConfig = json_decode($userSettingJson,true);

    }

    /**
     * 发送邮件
     * 其中type：1时为分派，2时关闭，3即将超时，4已超时
     * @param array $data 邮件内容
     * @return string
     * @throws \Exception
     */
    public function email ($data) {
        $emailService = new Email();
        $result = '';
        switch ($this->type) {
            case self::ASSIGN:
                {
                    $tpl = 'email.assign';
                    $subject = "【漏洞确认通知】".$this->subject . "系统";
                    break;
                }
            case self::SHUTDOWN:
                {
                    $tpl = 'email.shutdown';
                    $subject = "【漏洞关闭通知】".$this->subject . "系统";

                    break;
                }
            case self::ISTIMEOUT:
                {
                    $tpl = 'email.istimeout';
                    $subject = "【漏洞修复即将超时通知】".$this->subject . "系统";

                    break;
                }
            case self::HASTIMEOUT:
                {
                    $tpl = 'email.hastimeout';
                    $subject = "【漏洞修复已超时通知】".$this->subject . "系统";
                    break;
                }
            case self::UNCONFIRM:
                {
                    $tpl = 'email.unconfirm';
                    $subject = "【漏洞未修复通知】".$this->subject . "系统";
                    break;
                }
            case self::REJECT:
                {
                    $tpl = 'email.reject';
                    $subject = "【漏洞驳回通知】".$this->subject . "系统";
                    break;
                }
            default:
                {
                    $tpl = '';
                }
        }
        if (empty($tpl)) {
            throw new \Exception('please register a tpl');
        }
        $ccArr = config("email");
        // $cc = intval($ccArr['rcp_type']) === 0 ? '' : $ccArr['cc'];
        $cc = is_null($this->userConfig['cc']) ? '' : $this->userConfig['cc'];
        $data['cc'] = $cc;
        try {

            $userParameters = $this->emailData($tpl, $data, $cc, $subject);
            $result = $emailService->send($userParameters);
        } catch (\Exception $exception) {
            throw new \Exception($exception->getMessage());
        }

        return $result;
    }

    /**
     * 邮件相关内容属性
     * @param string $tmp 模板
     * @param array $data 数据
     * @param string $cc 抄送人
     * @param string $subject 主题 例如test应用
     * @return array
     * @throws \Exception
     */
    private function emailData ($tmp, $data, $cc, $subject) {
        View::addExtension('html', 'php');

        //排除测试用户
        $toUser = array_diff(explode(',', $data['useremail']), explode(',', config('email')['exclude']));
        if (empty($toUser)) {
            throw new \Exception('send email user not null');
        }
        $file = file_get_contents(view($tmp)->getPath());
        if (!$file) {
            throw new \Exception('send email content not null');
        }
        $content = $this->tplCreate($file, $data);
        $userParameters["StartDate"] = date("Y-m-d H:i:s", time());
        $userParameters["EndDate"] = date("Y-m-d H:i:s", time());
        $userParameters["To"] = implode(',', $toUser);
        $userParameters["Cc"] = $cc;

        $userParameters["Subject"] = $subject;
        $userParameters["Body"] = base64_encode($content);
        return $userParameters;
    }

    /**
     * 创建邮件模板
     * @param string $dir 模板文件
     * @param array $data 填充数据
     * @return string
     */
    private function tplCreate ($dir, $data) {
        $file = $dir;
        foreach ($data as $key => $value) {
            $search = '{{$' . $key . '}}';
            $file = str_replace($search, $value, $file);
        }
        return $file;
    }

    /**
     * 邮件正文内容
     * @param int $key
     * @param array $data
     * @param int $type
     * @return string
     */
    public function parseContent ($key, $data, $type) {
        $config = config('email');
        $title = $data->title;

        if ($data->level_id == Level::SERIOUSID) {
            // $isDay = $config['is_serious'];
            $isDay = $this->userConfig['istimeout']['serious'];
            // $outDay = $config['timeout_serious'];
            $outDay = $this->userConfig['hastimeout']['serious'];
        } elseif ($data->level_id == Level::HIGHRISKID) {
            // $isDay = $config['is_highrisk'];
            $isDay = $this->userConfig['istimeout']['ishighrisk'];
            // $outDay = $config['timeout_highrisk'];
            $outDay = $this->userConfig['hastimeout']['ishighrisk'];
        } else {
            // $isDay = $config['is_low'];
            $isDay = $this->userConfig['istimeout']['low'];
            // $outDay = $config['timeout_low'];
            $outDay = $this->userConfig['hastimeout']['low'];
        }
        if (isset($data->delayed_at)){
            $deadTime = date('m月d日',strtotime($data->delayed_at));
        }
        if (isset($data->deadTime)){
            $deadTime = date('m月d日',strtotime($data->deadTime));
        }
        $content = "<tr><td style=\"  padding: 10px;
            border-right: 1px solid #e7e7cd;
      border-bottom: 1px solid #e7e7cd;  text-align: center;\">" . $key . "</td><td  style=\"  padding: 10px;
           border-right: 1px solid #e7e7cd;
      border-bottom: 1px solid #e7e7cd;  text-align: center;\">" . $data->vuls_number . "</td><td style=\"  padding: 10px;
            border-right: 1px solid #e7e7cd;
      border-bottom: 1px solid #e7e7cd; text-align: left;\">" . $title . "</td><td style=\"  padding: 10px;
            border-right: 1px solid #e7e7cd;
      border-bottom: 1px solid #e7e7cd; text-align: center;color:red;\">" . $data->level_name . "</td>";
        if ($type == self::ASSIGN) {
            $content .= "<td style=\"padding: 10px;border-right: 1px solid #e7e7cd;border-bottom: 1px solid #e7e7cd; text-align: left;color:red;font-weight:bold;\">" . $data->deadline . "天内修复</td><td style=\"padding: 10px;border-right: 1px solid #e7e7cd;border-bottom: 1px solid #e7e7cd; text-align:left;\">" . $isDay . "天未修复，将有邮件提醒<br/>" . $outDay . "天仍未修复，将报送板块信息安全主管领导
</td>";
        }
        if ($type == self::ISTIMEOUT) {
            $content .= "<td style=\" padding: 10px;border-right: 1px solid #e7e7cd;border-bottom: 1px solid #e7e7cd; text-align: left;color:red;\">" . $deadTime . "修复</td><td style=\"  padding: 10px;border-right: 1px solid #e7e7cd;border-bottom: 1px solid #e7e7cd; text-align: left;\">" . $outDay . "天仍未修复，将报送板块信息安全主管领导</td>";
        }
        if ($type == self::HASTIMEOUT) {
            $content .= "<td style=\" padding: 10px;border-right: 1px solid #e7e7cd;border-bottom: 1px solid #e7e7cd; text-align: center;color:red;\">" . $deadTime . "修复</td><td style=\"  padding: 10px;
            border-right: 1px solid #e7e7cd;
      border-bottom: 1px solid #e7e7cd; text-align: center;\">" . $data->outDay . "</td>";
        }
        $content .= "</tr>";
        return $content;
    }

    /**
     * 邮件正文收件人信息
     * @param string $email
     * @param string $username
     * @return string
     */
    public function sendUsers ($email, $username) {
        $ret = '';
        $emailArr = explode(',', $email);
        $c = count($emailArr);
        $userArr = explode(',', $username);
        for ($i = 0; $i < $c; $i++) {
            $ret .= $emailArr[$i] . ';';
        }
        $ret = rtrim($ret, ';');
        $this->pushInfoElem('sendUsers', $ret);
        return $ret;
    }

    /**
     * 收件人信息，默认配置一个用户，若关掉配置，发送应用联系人
     * @param $email
     * @param $user
     * @return array
     */
    public function receiver ($email = '', $user = '') {
        $config = config('email');
        //没有配置邮件项,默认发送应用联系人
        if (intval($this->userConfig['rcp_type']) === 1) {
            //收件人邮箱
            $receiver['useremail'] = $this->userConfig['rcp_default'];
            $this->pushInfoElem('useremail', $this->userConfig['rcp_default']);
            //收件人
            $receiver['user'] = '';
            $this->pushInfoElem('user', '');
        } else {
            //收件人邮箱
            $receiver['useremail'] = $email;
            $this->pushInfoElem('useremail', $email);
            //收件人
            $receiver['user'] = $user;
            $this->pushInfoElem('user', $user);
        }
        return $receiver;
    }

    /**
     * 设置非正文公共信息
     * @param array $info
     */
    public function setInfo ($info = []) {

        $this->publics = $info;
    }

    /**
     * 追加非正文公共元素
     * @param $key
     * @param $val
     */
    public function pushInfoElem ($key, $val) {

        $this->publics[$key] = $val;
    }

    /**
     * @param $audit
     */
    public function audit ($audit) {
        $assignData['contents'] = '';
        $ids = [];
        foreach ($audit as $i => $assign) {
            //邮件类型
            $this->type = self::ASSIGN;
            //漏洞个数
            $assignData['count'] = ++$i;
            $assignData['contents'] .= $this->parseContent($i, $assign, self::ASSIGN);
            //id vulsid for send email success to update status
            $ids[$assign->id] = $assign->vulsid;
        }
        try {
            $this->email(array_merge($this->publics, $assignData));
            //确认邮件已发,更新为时间检测（即将过期）
            if (Task::whereIn('id', array_keys($ids))->update([
                    'sendtype' => self::ISTIMEOUT,
                    'updated_at' => Carbon::now()
                ]) > 0 && //更新邮件确认时间
                Vuls::whereIn('id', array_values($ids))->update([
                    'checksend' => 2,
                    'check_at' => Carbon::now()
                ]) > 0) {
                Log::info('vul['.implode(',',array_values($ids)).'] audit email send success.');
            }
        } catch (\Exception $e) {
            Log::info($e->getMessage());
        }
    }

    /**
     * @param $shutdown
     */
    public function shutdown ($shutdown) {
        $closeData['contents'] = '';
        $ids = [];
        foreach ($shutdown as $k => $close) {
            $this->type = self::SHUTDOWN;
            $closeData['count'] = ++$k;
            $closeData['contents'] .= $this->parseContent($k, $close, self::SHUTDOWN);
            $ids[$close->id] = $close->vulsid;
        }
        try {
            $this->email(array_merge($this->publics, $closeData));
            if (Task::whereIn('id', array_keys($ids))->forceDelete() > 0 && //更新邮件关闭时间
                Vuls::whereIn('id', array_values($ids))->update([
                    'shutsend' => 2,
                    'shut_at' => Carbon::now()
                ]) > 0) {

                Log::info('vul['.implode(',',array_values($ids)).'] shutdown email send success.');
            }
        } catch (\Exception $e) {
            Log::info($e->getMessage());
        }
    }

    /**
     * 即将超时
     * @param $data
     */
    public function isTimeOut ($data) {
        $isTimeOut = $this->timeOut($data);
        if($isTimeOut['isTimeOutKey'] > 0){

            try {
                $emailData['count']    = $isTimeOut['isTimeOutKey'];
                $emailData['contents'] = $isTimeOut['isTimeOut'];
                $this->email(array_merge($this->publics, $emailData));
                if (Task::whereIn('id', array_keys($isTimeOut['ids']))->update([
                        'sendtype' => self::HASTIMEOUT,
                        'updated_at' => Carbon::now()
                    ]) > 0) {

                    Log::info('vul['.implode(',',array_values($isTimeOut['ids'])).'] istimeout email send success.');
                }
            } catch (\Exception $e) {
                Log::info($e->getMessage());
            }
        }
    }
    /**
     * 已经超时
     * @param $data
     */
    public function hasTimeOut ($data) {
        $hasTimeOut = $this->timeOut($data);
        if ($hasTimeOut['hasTimeOutKey'] >0){

            try {
                $emailData['count']    = $hasTimeOut['hasTimeOutKey'];
                $emailData['contents'] = $hasTimeOut['hasTimeOut'];
                $this->email(array_merge($this->publics, $emailData));
                if (Task::whereIn('id', array_keys($hasTimeOut['ids']))->update([
                        'sendtype' => FALSE,
                        'updated_at' => Carbon::now()
                    ]) > 0) {

                    Log::info('vul['.implode(',',array_values($hasTimeOut['ids'])).'] hastimeout email send success.');
                }
            } catch (\Exception $e) {
                Log::info($e->getMessage());
            }
        }
    }
    /**
     * 超时处理
     * @param array $timeOut
     * @return array
     */
    private function timeOut ($timeOut) {
        $intDelayed_at = 0;
        $ret = ['isTimeOut' => '', 'hasTimeOut' => '', 'isTimeOutKey' => 0, 'hasTimeOutKey' => 0, 'ids' => []];

        foreach ($timeOut as $key => $item) {
            //计算不同类型漏洞时间
            //即将超时：严重漏洞2天、高危漏洞5天、中/低危漏洞25天还没有修复
            //已经超时：严重漏洞5天、高危漏洞10天、中/低危漏洞40天还没有修复的
            //发送确认邮件到现在的天数
            //如果延期时间通过，已延期时间为准
            //计算原有的到期时间 vuls.check_at(此处别名为send_time) + task.deadline(就是level表的term字段)

            $item->deadTime =Carbon::parse($item->send_time)->addDay($item->deadline);
            $sendTime = Carbon::parse($item->send_time);
            //邮件延时审核通过后此字段不为空，即：vuls.delayed_at
            if (!is_null($item->delayed_at)){
                //换算延长天数
                //将原来的到期时间与申请延时后的时间比，>0 则为延迟的天数，否则为0
                $intDelayed_at = $item->deadTime->diffInDays(Carbon::parse($item->delayed_at),false);
                $delayedDay = $intDelayed_at >0 ? $intDelayed_at : 0;
                //终止日延长几天等价于审核日延长几天
                //更新原来的审核日期即：vuls.check_at + 延迟的天数($delayedDay)
                $sendTime = Carbon::parse($item->send_time)->addDay($delayedDay);
                //新到期时间
                $item->deadTime =Carbon::parse($item->delayed_at);
            }
            $item->outDay = $item->deadTime->diffInDays(Carbon::now(), false);
            $isTimeDay = $sendTime->diffInDays(Carbon::now(), false);
            /*Log::info($item.
                "模拟当前日期：".date('y-m-d',time()).
                "延迟天数(原来审核时间+期限与当前设置到期时间做比较)：".$intDelayed_at.
                "延迟后的日期：".$item->delayed_at.
                "原审核日期：".$item->send_time.
                "申请延时后的审核日期距当前日期：".$isTimeDay.
                "申请延时后的审核日期：".$sendTime.
                "超时天数：".$item->outDay
            );*/
            switch ($item['level_id']) {
                case Level::SERIOUSID:
                    if ($isTimeDay >= $this->userConfig['istimeout']['serious']) {
                        ++$ret['isTimeOutKey'];
                        $ret['isTimeOut'] .= $this->parseContent($ret['isTimeOutKey'], $item, self::ISTIMEOUT);
                    }
                    if ($isTimeDay >= $this->userConfig['hastimeout']['serious']) {

                        ++$ret['hasTimeOutKey'];
                        $ret['hasTimeOut'] .= $this->parseContent($ret['hasTimeOutKey'], $item, self::HASTIMEOUT);
                    }
                    break;
                case Level::HIGHRISKID:
                    if ($isTimeDay >= $this->userConfig['istimeout']['ishighrisk']) {
                        ++$ret['isTimeOutKey'];
                        $ret['isTimeOut'] .= $this->parseContent($ret['isTimeOutKey'], $item, self::ISTIMEOUT);
                    }
                    if ($isTimeDay >= $this->userConfig['hastimeout']['ishighrisk']) {
                        ++$ret['hasTimeOutKey'];
                        $ret['hasTimeOut'] .= $this->parseContent($ret['hasTimeOutKey'], $item, self::HASTIMEOUT);
                    }
                    break;
                case Level::INRISKID:
                    if ($isTimeDay >= $this->userConfig['istimeout']['low']) {
                        ++$ret['isTimeOutKey'];
                        $ret['isTimeOut'] .= $this->parseContent($ret['isTimeOutKey'], $item, self::ISTIMEOUT);
                    }
                    if ($isTimeDay >= $this->userConfig['hastimeout']['low']) {
                        ++$ret['hasTimeOutKey'];;
                        $ret['hasTimeOut'] .= $this->parseContent($ret['hasTimeOutKey'], $item, self::HASTIMEOUT);
                    }
                    break;
                case Level::LOWID:

                    if ($isTimeDay >= $this->userConfig['istimeout']['low']) {
                        ++$ret['isTimeOutKey'];
                        $ret['isTimeOut'] .= $this->parseContent($ret['isTimeOutKey'], $item, self::ISTIMEOUT);
                    }
                    if ($isTimeDay >= $this->userConfig['hastimeout']['low']) {
                        ++$ret['hasTimeOutKey'];
                        $ret['hasTimeOut'] .= $this->parseContent($ret['hasTimeOutKey'], $item, self::HASTIMEOUT);
                    }
                    break;
            }
            $ret['ids'][$item->id] = $item->vulsid;
        }
        return $ret;

    }

    /**
     * 确认未修复及时邮件
     */
    public function unconfirm ($unconfirm) {

        //邮件类型
        $this->type = self::UNCONFIRM;
        //漏洞个数
        $emailData['count'] = 1;
        $emailData['contents'] = $this->parseContent(1, $unconfirm, self::UNCONFIRM);
        try {
            $ret = $this->email(array_merge($this->publics, $emailData));
            Log::info('vul['.$unconfirm->id.'] unconfirm email send info:'.json_encode($ret));
        } catch (\Exception $e) {
            Log::info($e->getMessage());
        }
    }

    /**
     * 驳回申诉邮件
     * @param array $reject
     */
    public function reject ($reject) {

        //邮件类型
        $this->type = self::REJECT;
        //漏洞个数
        $emailData['count'] = 1;
        $emailData['contents'] = $this->parseContent(1, $reject, self::REJECT);
        try {
            $ret = $this->email(array_merge($this->publics, $emailData));
            Log::info('vul['.$reject->id.'] reject email send info:'.json_encode($ret));
        } catch (\Exception $e) {
            Log::info($e->getMessage());
        }
    }
}